using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using System.Runtime.InteropServices;
using System.Collections.Specialized;
using System.IO;
using WMEncoderLib;
using System.Threading;
using System.Collections;
using System.Diagnostics;
using System.Windows.Forms;

namespace WMVConvert
{
	public class ConvertMedia: Task, IProfileConfig, IEncoderConfig
	{
		ITaskItem[] inputVideo;

		[Required]
		public ITaskItem[] InputVideo
		{
			get
			{
				return inputVideo;
			}
			set
			{
				inputVideo = value;
			}
		}

		private VideoPreset videoPreset;

		public string Preset
		{
			get { return videoPreset.Name; }
			set { videoPreset = VideoPreset.GetPreset(value); }
		}

		public VideoPreset VideoPreset
		{
			get { return videoPreset; }
		}

		List<ITaskItem> outputVideo;

		[Output]
		public ITaskItem[] OutputVideo
		{
			get
			{
				return outputVideo.ToArray();
			}
			set
			{
				outputVideo.Clear();
				outputVideo.AddRange(value);
			}
		}

		Dictionary<string, Dictionary<int,IMediaInfo>> splits;

		public ConvertMedia()
		{
			splits = new Dictionary<string, Dictionary<int, IMediaInfo>>();
			outputVideo = new List<ITaskItem>();
		}

		public override bool Execute()
		{
			foreach (ITaskItem item in inputVideo)
			{
				MediaTaskItem mediaItem = new MediaTaskItem(item);
				string source = item.GetMetadata("Source");
				source = new FileInfo(source).FullName;
				if (!splits.ContainsKey(source))
				{
					splits.Add(source, new Dictionary<int, IMediaInfo>());
				}
				Dictionary<int, IMediaInfo> list = splits[source];
				list.Add(mediaItem.Segment, mediaItem);
			}
			foreach(string source in splits.Keys)
			{
				bool splitsConverted = true;
				FileInfo fi = new FileInfo(source);
				string target = Path.Combine(fi.Directory.FullName, Path.GetFileNameWithoutExtension(fi.Name) + ".wmv");
				if (File.Exists(target))
				{
					AddConvertedMedia(target, source);
					Log.LogMessage("Target file {0} for source {1} already done", target, source);
					continue;
				}
				Dictionary<int, IMediaInfo> parts = splits[source];
				foreach (int segment in parts.Keys)
				{
					if (!Convert(parts[segment]))
					{
						splitsConverted = false;
						break;
					}
				}
				if (splitsConverted && Join(target,parts))
				{
					if (File.Exists(target))
					{
						AddConvertedMedia(target,source);
					}
				}
			}
			return true;
		}

		private void MoveSource(string source)
		{
			DirectoryInfo di = null;
			try
			{
				if (!Directory.Exists(TrashFolder))
				{
					Directory.CreateDirectory(TrashFolder);
				}
				di = new DirectoryInfo(TrashFolder);
			}
			catch
			{
				di = null;
			}
			if (di != null)
			{
				try
				{
					FileInfo fi = new FileInfo(source);
					string destFile = Path.Combine(TrashFolder, fi.Name);
					File.Move(source, destFile);
				}
				catch(Exception e)
				{
					Log.LogWarningFromException(e);
				}
			}
		}

		private void AddConvertedMedia(string target,string source)
		{
			try
			{
				SetMetadata setMetadata = new SetMetadata(Log);
				setMetadata.BuildEngine = BuildEngine;
				setMetadata.InputVideo = new ITaskItem[] { new TaskItem(target) };
				setMetadata.Force = true;
				setMetadata.Execute();
			}
			catch
			{
			}
			TaskItem convertedItem = new TaskItem(target);
			convertedItem.SetMetadata("Source", source);
			outputVideo.Add(convertedItem);
			MoveSource(source);
		}

		private bool ExecuteProcess(string processExe,string commandLine)
		{
			ProcessStartInfo oInfo = new ProcessStartInfo(processExe, commandLine);
			oInfo.UseShellExecute = false;
			oInfo.ErrorDialog = false;
			oInfo.CreateNoWindow = true;
			oInfo.RedirectStandardOutput = true;
			try
			{
				Process p = Process.Start(oInfo);
				using(StreamReader oReader2 = p.StandardOutput)
				{
					while(!oReader2.EndOfStream)
					{
						Log.LogCommandLine(oReader2.ReadLine());
					}
				}
				return (p.ExitCode == 0);
			}
			catch (Exception ex)
			{
				Log.LogError(ex.Message);
				return false;
			}
		}

		private bool Join(string target,Dictionary<int,IMediaInfo> parts)
		{
			StringBuilder asfBinCmdLine = new StringBuilder();
			long currentStart = 0;
			foreach (int segment in parts.Keys)
			{
				IMediaInfo mediaItem = parts[segment];
				using (MediaInfo targetInfo = new MediaInfo(mediaItem.TargetFile))
				{
					asfBinCmdLine.Append(" -i \"");
					asfBinCmdLine.Append(mediaItem.TargetFile);
					asfBinCmdLine.Append("\" ");
					asfBinCmdLine.Append(" -start ");
					asfBinCmdLine.Append((double)(currentStart) / 1000.0);
					asfBinCmdLine.Append(" -dur ");
					long duration = Math.Min(SegmentSize,targetInfo.PlayTime);
					if (segment == parts.Keys.Count - 1)
					{
						duration = targetInfo.PlayTime;
					}
					asfBinCmdLine.Append((double)duration / 1000.0);
					currentStart += targetInfo.PlayTime;
				}
			}
			asfBinCmdLine.Append(" -o \"");
			asfBinCmdLine.Append(target);
			asfBinCmdLine.Append("\" -y ");
			//asfBinCmdLine.Append(" -istart -cvb ");
			if (ExecuteProcess(@".\asfbin.exe", asfBinCmdLine.ToString()))
			{
				try
				{
					//delete parts
					if (DeleteParts)
					{
						foreach (IMediaInfo mediaInfo in parts.Values)
						{
							File.Delete(mediaInfo.TargetFile);
						}
					}
				}
				catch (Exception e)
				{
					Log.LogWarningFromException(e);
				}
				return true;
			}
			return false;
		}

		private bool Reindex(string input,string target,int duration)
		{
			StringBuilder asfBinCmdLine = new StringBuilder();
			asfBinCmdLine.Append(" -i \"");
			asfBinCmdLine.Append(input);
			asfBinCmdLine.Append("\" -start 0.0");
			asfBinCmdLine.Append(" -dur ");
			double dur = duration;
			dur = dur / 1000;
			asfBinCmdLine.Append(dur);
			asfBinCmdLine.Append(" -o \"");
			asfBinCmdLine.Append(target);
			asfBinCmdLine.Append("\" -y");
			if (ExecuteProcess(@".\asfbin.exe", asfBinCmdLine.ToString()))
			{
				try
				{
					//delete parts
					if (DeleteParts)
					{
						File.Delete(input);
					}
				}
				catch (Exception e)
				{
					Log.LogWarningFromException(e);
				}
				return true;
			}
			return false;
		}

		private const double ProgressDelta = 0.01;
		private const int ConvertTimeOut = 5; // in minutes

		private bool Convert(IMediaInfo mediaTaskItem)
		{
			try
			{
				FileInfo fi = new FileInfo(mediaTaskItem.TargetFile);
				if (File.Exists(fi.FullName))
				{
					Log.LogMessage("Segment {0} for {1} already done", mediaTaskItem.Segment+1, mediaTaskItem.SourceFile);
					return true;
				}
				Log.LogMessage("Converting Segment {2} for {0} to {1}...", mediaTaskItem.SourceFile, mediaTaskItem.TargetFile, mediaTaskItem.Segment+1);
				string tempOutFile = Path.Combine(fi.Directory.FullName, Path.GetFileNameWithoutExtension(fi.Name) + ".temp");
				int numberOfSegments = (int)((mediaTaskItem.PlayTime / SegmentSize));
				using (Encoder encoder = new Encoder(tempOutFile, mediaTaskItem, this, Log))
				{
					using (Profile profile = new Profile(Log, mediaTaskItem, this))
					{
						ApplicationContext appContext = new ApplicationContext();
						encoder.Reset(profile);
						if (!encoder.IsValid)
						{
							return false;
						}
						encoder.Start();
						double lastProgress = -1.0 * ProgressDelta;
						TimeSpan lastTime = new TimeSpan(DateTime.Now.Ticks);
						while (encoder.State != WMENC_ENCODER_STATE.WMENC_ENCODER_STOPPED)
						{
							double progress = encoder.Progress;
							TimeSpan elapsedTime = new TimeSpan(DateTime.Now.Ticks);
							elapsedTime = elapsedTime - lastTime;
							bool timeOut = (elapsedTime.TotalMinutes >= ConvertTimeOut);
							if (progress >= 1.0 || timeOut )
							{
								//Don't wait for index to finish
								if (timeOut && mediaTaskItem.Segment < numberOfSegments)
								{
									Log.LogWarning("Timeout...giving up");
									return false;
								}
								break;
							}
							if (progress - lastProgress >= ProgressDelta)
							{
								lastProgress = progress;
								lastTime = new TimeSpan(DateTime.Now.Ticks);
								Log.LogMessage("	Segment {0:000}:{1:00.00%} of {2:000}", 
									mediaTaskItem.Segment+1,lastProgress, 
									numberOfSegments+1);
							}
							Thread.Sleep(1000);
						}
						encoder.Stop();
						File.Move(tempOutFile, mediaTaskItem.TargetFile);
						return true;
					}
				}
			}
			catch (Exception exception)
			{
				Log.LogError(exception.Message);
				return false;
			}
		}

		bool deleteParts;

		public bool DeleteParts
		{
			get { return deleteParts; }
			set { deleteParts = value; }
		}

		string trashFolder;

		[Required]
		public string TrashFolder
		{
			get { return trashFolder; }
			set { trashFolder = value; }
		}

		int videoMaxBitRate;

		[Required]
		public int VideoMaxBitRate
		{
			get { return videoMaxBitRate; }
			set { videoMaxBitRate = value; }
		}

		#region IProfileConfig Members

		string videoCodec = Constants.WVC1;

		public string VideoCodec
		{
			get { return videoCodec; }
			set { videoCodec = value; }
		}

		int videoQuality = 98;

		public int VideoQuality
		{
			get { return videoQuality; }
			set { videoQuality = value; }
		}

		string audioCodec = Constants.WMAPRO;

		public string AudioCodec
		{
			get { return audioCodec; }
			set { audioCodec = value; }
		}

		int videoKeyFrameDistance = 2000;

		public int VideoKeyFrameDistance
		{
			get { return videoKeyFrameDistance; }
			set { videoKeyFrameDistance = value; }
		}

		int videoImageSharpness = 100;

		public int VideoImageSharpness
		{
			get { return videoImageSharpness; }
			set { videoImageSharpness = value; }
		}

		int videoBufferSize = 3000;

		public int VideoBufferSize
		{
			get { return videoBufferSize; }
			set { videoBufferSize = value; }
		}

		int videoBufferMax = 5000; 

		public int VideoBufferMax
		{
			get { return videoBufferMax; }
			set { videoBufferMax = value; }
		}

		WMENC_PROFILE_VBR_MODE audioBitRateMode = WMENC_PROFILE_VBR_MODE.WMENC_PVM_NONE;

		public WMENC_PROFILE_VBR_MODE AudioBitRateMode
		{
			get { return audioBitRateMode; }
			set { audioBitRateMode = value; }
		}

		WMENC_PROFILE_VBR_MODE videoBitRateMode = WMENC_PROFILE_VBR_MODE.WMENC_PVM_NONE;

		public WMENC_PROFILE_VBR_MODE VideoBitRateMode
		{
			get { return videoBitRateMode; }
			set { videoBitRateMode = value; }
		}

		#endregion

		#region IEncoderConfig Members

		int segmentSize;

		[Required]
		public int SegmentSize
		{
			get { return segmentSize; }
			set { segmentSize = value; }
		}

		int segmentDelta = 4000;

		public int SegmentDelta
		{
			get { return segmentDelta; }
			set { segmentDelta = value; }
		}

		#endregion

	}
}
